package device

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"energy-amiot-platform/core"
	"energy-amiot-platform/model/dbspo"
	"energy-amiot-platform/model/mqttmsg"
	"energy-amiot-platform/model/redispo"
	"energy-amiot-platform/model/request"
	"energy-amiot-platform/model/response"
	"energy-amiot-platform/repository"
	"energy-amiot-platform/utils/respout"
	"errors"
	"fmt"
	"strings"
	"time"

	mqtt "github.com/eclipse/paho.mqtt.golang"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/jinzhu/copier"
	"gorm.io/gorm"
)

var (
	moduleCode = 4
)

// @Tags 设备管理
// @Summary 添加一个设备
// @Description 添加一个设备
// @Accept  json
// @Product json
// @Param   input body   request.DeviceAddReq{}  true		"设备添加数据"
// @Success 200 {object} response.Response{result=response.DeviceAddedContent} "Ok"
// @Router /device [post]
func create(ctx *gin.Context) {
	var handleCode = 1
	var req request.DeviceAddReq

	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	nodeId := req.NodeId
	if req.DeviceName == "" {
		req.DeviceName = nodeId
	}

	if req.Password == "" {
		req.Password = nodeId
	}

	err = core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		var product dbspo.Product
		tx1 := tx.Find(&product, "id=?", req.ProductId)
		if tx1.Error != nil {
			return tx1.Error
		}
		if tx1.RowsAffected == 0 {
			return gorm.ErrRecordNotFound
		}

		result, err1 := createDevice(tx, &req, &product)
		if err1 != nil {
			return err1
		}

		respout.Ok(ctx, result)
		return nil
	})

	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
		return
	}
}

// @Tags 设备管理
// @Summary 获取设备集
// @Description 获取设备集
// @Product json
// @Param   companyId query   	string true			"企业标识"
// @Param   productId query   	string false			"产品标识"
// @Param   imei query   		string false			"imei"
// @Param   deviceId query   	string false			"设备标识"
// @Param   deviceName query   	int false			"设备名称"
// @Param   pageSize query   	int false			"每页数量"	default(10)
// @Param   pageNum query   	int false			"第几页"		default(1)
// @Success 200 {object} response.Response{result=response.PageData{list=[]dbspo.Device}} "Ok"
// @Router /device [get]
func pagingQuery(ctx *gin.Context) {
	var handleCode = 3
	var req request.DevicePagingQueryReq
	err := ctx.ShouldBindQuery(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	if req.PageNum == 0 {
		req.PageNum = 1
	}

	if req.PageSize == 0 {
		req.PageSize = 10
	}

	var count int64
	var result []dbspo.Device
	db := core.DATABASE.Cur().Table("hv_device").Where("pid=?", req.CompanyId)
	if req.ProductId != 0 {
		db.Where("cid=?", req.ProductId)
	}
	if req.Imei != "" {
		db.Where("imei LIKE ?", "%"+req.Imei+"%")
	}
	if req.DeviceName != "" {
		db.Where("device_name LIKE ?", "%"+req.DeviceName+"%")
	}
	if req.DeviceId != "" {
		db.Where("device_id LIKE ?", "%"+req.DeviceId+"%")
	}

	dbc := db.WithContext(context.Background())
	if err = dbc.Count(&count).Error; err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
		return
	}

	offset := req.PageSize * (req.PageNum - 1)
	err = dbc.Omit("pid").Offset(offset).Limit(req.PageSize).Scan(&result).Error
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, err, nil)
		return
	}

	respout.Ok(ctx, response.PageData{
		Total: count,
		List:  result,
	})
	return
}

// @Tags 设备管理
// @Summary 删除设备
// @Description 删除设备
// @Product json
// @Param   did path string true		"设备标识"
// @Success 200 {object} response.Response{} "Ok"
// @Router /device/{did} [delete]
func delete(ctx *gin.Context) {
	deleteDevice(ctx)
}

// @Tags 设备管理
// @Summary 添加一个设备（客户）
// @Description 添加一个设备（客户）
// @Accept  json
// @Product json
// @Param   input body   request.DeviceAddReq{}  true		"设备添加数据"
// @Success 200 {object} response.Response{result=response.DeviceAddedContent} "Ok"
// @Router /client/device [post]
func createForClient(ctx *gin.Context) {
	var handleCode = 1
	var req request.DeviceAddReqForClient
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	nodeId := req.NodeId
	if req.DeviceName == "" {
		req.DeviceName = nodeId
	}

	if req.Password == "" {
		req.Password = nodeId
	}

	value, _ := ctx.Get("product")
	var product = value.(*dbspo.Product)
	var req2 request.DeviceAddReq

	copier.Copy(&req2, &req)
	err = core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		result, err1 := createDevice(tx, &req2, product)
		if err1 != nil {
			return err1
		}

		respout.Ok(ctx, result)
		return nil
	})

	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
	}
}

// @Tags 设备管理
// @Summary 批量添加设备（客户）
// @Description 批量添加设备（客户）
// @Accept multipart/form-data
// @Product json
// @Param file formData file true "file"
// @Param input body request.DeviceBatchCreateDto true "input"
// @Success 200 {object} response.Response{result=response.DeviceBatchAddResult} "Ok"
// @Router /client/device/batch [post]
func batchCreate(ctx *gin.Context) {
	var handleCode = 2
	v, _ := ctx.Get("product")
	product := v.(*dbspo.Product)

	var req request.DeviceBatchCreateDto
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	if len(req.List) > 100 {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrErrParse, errors.New("批量添加数量最多100个"), nil)
		return
	}

	// 循环
	var result response.DeviceBatchAddResult
	core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		for i := 0; i < len(req.List); i++ {
			var item = req.List[i]
			item.Password = item.NodeId
			if item.NodeId == "" {
				result.Ignores = append(result.Ignores, item.NodeId)
				continue
			}
			if item.DeviceName == "" {
				item.DeviceName = item.NodeId
			}
			if item.Password == "" {
				item.Password = item.NodeId
			}

			s := uuid.New().String()
			var deviceId = strings.Replace(s, "-", "", -1)
			var dpo dbspo.Device
			var rpo redispo.Device
			var authUserRpo redispo.AuthUser
			var salt = "hkfg+"

			dpo.Imei = item.NodeId
			dpo.Cid = product.ID
			dpo.DeviceId = deviceId
			dpo.DeviceName = item.DeviceName

			rpo.Registered = 1
			rpo.CompanyID = product.Cid
			rpo.DeviceName = item.DeviceName
			rpo.ProductID = product.ID

			authUserRpo.Super = false
			authUserRpo.Salt = salt
			authUserRpo.PasswordHash = shapwd(item.Password, salt)

			tx1 := repository.DEVICE.Create(tx, &dpo)
			if tx1.Error != nil {
				respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, tx.Error, nil)
				return tx1.Error
			}

			background := context.Background()
			pipeline := core.REDIS.Client.Pipeline()
			// 设置认证
			pipeline.HMSet(background, "mqtt_user:"+item.NodeId, authUserRpo)
			// 添加设备
			pipeline.HMSet(background, "mqtt_device:"+item.NodeId, rpo)
			topic1 := fmt.Sprintf(core.TopicPatternDevicePropertyPost, product.ID, item.NodeId)
			topic2 := fmt.Sprintf(core.TopicPatternDevicePropertySet, product.ID, item.NodeId)
			topic3 := fmt.Sprintf(core.TopicPatternDeviceBootPoll, item.NodeId)
			topic4 := fmt.Sprintf(core.TopicPatternDeviceBootReply, item.NodeId)
			// 设置acl
			pipeline.HSet(background, "mqtt_acl:"+item.NodeId, topic1, "publish")
			pipeline.HSet(background, "mqtt_acl:"+item.NodeId, topic2, "subscribe")
			pipeline.HSet(background, "mqtt_acl:"+item.NodeId, topic3, "publish")
			pipeline.HSet(background, "mqtt_acl:"+item.NodeId, topic4, "subscribe")
			_, err = pipeline.Exec(background)

			if err != nil {
				result.Fails = append(result.Fails, item.NodeId)
				continue
			}

			result.Adds = append(result.Adds, response.DeviceAddedContent{NodeId: item.NodeId, DeviceId: deviceId, DeviceName: item.DeviceName})
		}

		// 发送设备删除消息，暂时放这里了
		for _, item := range result.Adds {
			// 发送设备注册消息，暂时放这里了
			go func(client mqtt.Client, productId uint64, id string) {
				var deviceStatInfo mqttmsg.DeviceStatInfo
				deviceStatInfo.NodeId = id
				deviceStatInfo.OperatedAt = time.Now().Unix()
				marshal, _ := json.Marshal(&deviceStatInfo)
				client.Publish(fmt.Sprintf(core.TopicPatternDeviceEventReg, productId, id), 3, false, marshal)

			}(core.MQTT.Client, product.ID, item.NodeId)
		}

		respout.Ok(ctx, result)
		return nil
	})
}

// @Tags 设备管理
// @Summary 删除设备（客户）
// @Description 删除设备（客户）
// @Product json
// @Param   imei query string false				"imei"
// @Param   deviceId query string false			"设备标识"
// @Param   deviceName query string false		"设备名称"
// @Param   pageSize query int false			"每页数量" 	default(10)
// @Param   pageNum query  int false			"第几页" 	default(1)
// @Success 200 {object} response.Response{result=response.PageData{list=[]dbspo.Device}} "Ok"
// @Router /client/device [get]
func pagingQueryFormClient(ctx *gin.Context) {
	var handleCode = 3
	var req request.DevicePagingQueryForClientReq
	err := ctx.ShouldBind(&req)
	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, err, nil)
		return
	}

	if req.PageNum == 0 {
		req.PageNum = 1
	}

	if req.PageSize == 0 {
		req.PageSize = 10
	}

	product := ctx.Value("product").(*dbspo.Product)

	var count int64
	var result []dbspo.Device
	db := core.DATABASE.Cur().Model(&dbspo.Device{})
	if req.Imei != "" {
		db.Where("imei LIKE ?", "%"+req.Imei+"%")
	}
	if req.DeviceName != "" {
		db.Where("device_name LIKE ?", "%"+req.DeviceName+"%")
	}
	if req.DeviceId != "" {
		db.Where("device_id LIKE ?", "%"+req.DeviceId+"%")
	}
	db = db.WithContext(context.Background())

	if err = db.Where("cid=?", product.ID).Count(&count).Error; err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 1, respout.StrFail, err, nil)
		return
	}

	offset := req.PageSize * (req.PageNum - 1)
	err = db.Model(&dbspo.Device{}).Where("cid=?", product.ID).Offset(offset).Limit(req.PageSize).Scan(&result).Error

	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 2, respout.StrFail, err, nil)
		return
	}

	respout.Ok(ctx, response.PageData{
		Total: count,
		List:  result,
	})
	return
}

// @Tags 设备管理
// @Summary 删除设备（客户）
// @Description 删除设备（客户）
// @Accept  multipart/form-data
// @Product json
// @Param   did path string true		"设备标识"
// @Success 200 {object} response.Response{} "Ok"
// @Router /client/device/{did} [delete]
func deleteForClient(ctx *gin.Context) {
	deleteDevice(ctx)
}

func createDevice(tx *gorm.DB, req *request.DeviceAddReq, product *dbspo.Product) (*response.DeviceAddedContent, error) {
	var deviceRpo redispo.Device
	var authUserRpo redispo.AuthUser
	var deviceDpo dbspo.Device

	u, _ := uuid.NewUUID()
	deviceId := strings.Replace(u.String(), "-", "", -1)
	var salt = "hkfg+"

	authUserRpo.PasswordHash = shapwd(req.Password, salt)
	authUserRpo.Super = false
	authUserRpo.Salt = salt

	deviceRpo.DeviceName = req.DeviceName
	deviceRpo.ProductID = product.ID
	deviceRpo.CompanyID = product.Cid
	deviceRpo.DeviceID = deviceId
	deviceRpo.Registered = 1

	deviceDpo.Imei = req.NodeId
	deviceDpo.DeviceId = deviceId
	deviceDpo.DeviceName = req.DeviceName
	deviceDpo.Cid = product.ID

	tx1 := repository.DEVICE.Create(tx, &deviceDpo)
	if tx1.Error != nil {
		return nil, tx1.Error
	}

	pipeline := core.REDIS.Client.Pipeline()
	background := context.Background()
	// 设置认证
	pipeline.HMSet(background, "mqtt_user:"+req.NodeId, authUserRpo)
	// 添加设备
	pipeline.HMSet(background, "mqtt_device:"+req.NodeId, deviceRpo)
	topic1 := fmt.Sprintf(core.TopicPatternDevicePropertyPost, product.ID, req.NodeId)
	topic2 := fmt.Sprintf(core.TopicPatternDevicePropertySet, product.ID, req.NodeId)
	topic3 := fmt.Sprintf(core.TopicPatternDeviceBootPoll, req.NodeId)
	topic4 := fmt.Sprintf(core.TopicPatternDeviceBootReply, req.NodeId)
	// 设置acl
	pipeline.HSet(background, "mqtt_acl:"+req.NodeId, topic1, "publish")
	pipeline.HSet(background, "mqtt_acl:"+req.NodeId, topic2, "subscribe")
	pipeline.HSet(background, "mqtt_acl:"+req.NodeId, topic3, "publish")
	pipeline.HSet(background, "mqtt_acl:"+req.NodeId, topic4, "subscribe")
	_, err := pipeline.Exec(background)
	if err != nil {
		return nil, err
	}

	// 发送设备注册消息，暂时放这里了
	go func(client mqtt.Client, productId uint64, id string) {
		var miotDeviceStatResp mqttmsg.MiotDeviceStatResp
		mil := time.Now().UnixMilli()
		miotDeviceStatResp.Service = "device_reg"
		miotDeviceStatResp.Identity = mil
		miotDeviceStatResp.Data.NodeId = id
		miotDeviceStatResp.Data.Stat = "reg"
		miotDeviceStatResp.Data.OperatedAt = mil / 1000
		marshal, _ := json.Marshal(&miotDeviceStatResp)
		client.Publish(fmt.Sprintf(core.TopicPatternDeviceEventReg, productId, id), 0, false, marshal)

	}(core.MQTT.Client, product.ID, req.NodeId)

	result := response.DeviceAddedContent{}
	result.NodeId = req.NodeId
	result.DeviceId = deviceId
	result.DeviceName = req.DeviceName

	return &result, nil
}

func deleteDevice(ctx *gin.Context) {
	var handleCode = 4
	deviceId := ctx.Param("did")
	if deviceId == "" {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 0, respout.StrErrParse, errors.New("设备标识为空"), nil)
		return
	}

	err := core.DATABASE.Cur().Transaction(func(tx *gorm.DB) error {
		tx1, device := repository.DEVICE.FindByDeviceId(tx, deviceId)
		if tx1.Error != nil {
			return tx1.Error
		}
		if tx1.RowsAffected == 0 {
			return gorm.ErrRecordNotFound
		}

		tx1 = repository.DEVICE.DeleteByDevId(tx, deviceId)
		if tx1.Error != nil {
			return tx1.Error
		}

		pipeline := core.REDIS.Pipeline()
		background := context.Background()
		pipeline.Del(background, "mqtt_user:"+device.Imei)
		pipeline.Del(background, "mqtt_acl:"+device.Imei)
		pipeline.Del(background, "mqtt_device:"+device.Imei)
		_, err1 := pipeline.Exec(background)
		if err1 != nil {
			return err1
		}

		// 发送设备删除消息，暂时放这里了
		go func(client mqtt.Client, productId uint64, imei string) {
			var deviceStatInfo mqttmsg.DeviceStatInfo
			var deviceStatResp mqttmsg.MiotDeviceStatResp
			unix := time.Now().Unix()
			deviceStatInfo.NodeId = imei
			deviceStatInfo.Stat = "del"
			deviceStatInfo.OperatedAt = unix
			deviceStatResp.Identity = unix
			deviceStatResp.Service = "device_del"
			marshal, _ := json.Marshal(&deviceStatResp)
			core.MQTT.Client.Publish(fmt.Sprintf(core.TopicPatternDeviceEventDel, productId, deviceId), 0, false, marshal)

		}(core.MQTT.Client, device.Cid, device.Imei)

		respout.Ok(ctx, nil)
		return nil
	})

	if err != nil {
		respout.SendError(ctx, core.APP.Code, moduleCode, handleCode, 3, respout.StrFail, err, nil)
	}
}

func shapwd(password, salt string) string {
	bytes := []byte(password)
	hash := sha256.New()
	bytes = append(bytes, salt...)
	hash.Write(bytes)
	hashedPasswordBytes := hash.Sum(nil)
	return hex.EncodeToString(hashedPasswordBytes)
}
